#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>

#define DAY_SIZE 4

#define WEEK_SIZE 7

#define DAY_ROW_SIZE DAY_SIZE * WEEK_SIZE

#define DAY_ROW_ARR_SIZE DAY_SIZE* WEEK_SIZE + 1

#define MIN_YEAR 1970

enum sunsort {sunIsFirst, sunIsEnd};

typedef enum Dateunit {year, month} dateunit;

int mdays[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

char *months[] = {"", "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"};

char *weeks[] = {"Sun","Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};

enum sunsort wsort = sunIsFirst;

//存储错误信息
char errinfo[50];


int dayofweek(int y, int m, int d)
{
    if (m == 1 || m == 2)
        (y--, m += 12);

    return (y + y/4 - y/100 + y/400 + (13*m+8)/5 + d) % WEEK_SIZE;
}

int is_leap(int y)
{
    return y % 400 == 0 || (y % 4 == 0 && y % 100 != 0);
}

int dayNl(int wd)
{
    return (wd + (1 - wsort)) % WEEK_SIZE == 0;
}

int monthdays(int y, int m)
{
    if (m -- != 2)
        return mdays[m];

    return mdays[m] + is_leap(y);
}

void push_calendar(int y, int m, char (*cal)[DAY_ROW_ARR_SIZE])
{
    int wd = dayofweek(y, m, 1);
    int ds = monthdays(y, m);
    int k = 0;

    char tmp[DAY_SIZE]; //第一行至少存在一天

    sprintf(cal[k++], "%10d / %02d", y, m);

    //这里如果不这么处理会出现乱码,原因不清楚
    for (k=1; k<WEEK_SIZE; ++k) {
        cal[k][0] = '\0';
    }
    k=1;

    sprintf(cal[k], "%*s", DAY_SIZE * (wsort == sunIsFirst? wd: ((wd -1 + WEEK_SIZE) % WEEK_SIZE)), "");

    for (int i=1; i<=ds; ++i) {
        sprintf(tmp, "%*s%02d", DAY_SIZE-2, "", i);

        strcat(cal[k], tmp);

        if (dayNl(wd++)) ++k;
    }

    wd--;

    if (dayNl(wd))
        k--;
    else {
        sprintf(tmp, "%*s", DAY_SIZE, "");

        for (int i=(wd+1) % WEEK_SIZE; i < WEEK_SIZE; i++)
            strcat(cal[k], tmp);
    }

    while (++k < WEEK_SIZE) {
        sprintf(cal[k], "%*s", DAY_ROW_SIZE, "");
    }
}

/**
 * 输出行日历间的空格
 * @param int c 当前日历索引
 * @param int n 总日历数
 */
void print_space_per_calendar(int c, int n) {
    if (c < n-1)printf("  ");
}


/**
 * 是否显示日历的最后一行
 * 如果当前行的所有日历最后一行都没有天数,则不显示
 *
 * @param const char *cal 一行日历数组
 * @param int n 列数
 */
int show_last_row(const char (*cal)[WEEK_SIZE][DAY_ROW_ARR_SIZE], int n)
{
    const char *c;

    while (--n >= 0) {
        c = cal[n][WEEK_SIZE-1];
        while (*c) {
            if (*c++ != ' ') {
                return 1;
            }
        }
    }

    return 0;
}

void print_calendar(const char (*cal)[WEEK_SIZE][DAY_ROW_ARR_SIZE], int n)
{

    static char line[DAY_ROW_ARR_SIZE];

    if (!strlen(line)) {
        for (int i=0; i<DAY_ROW_SIZE; ++i)
            line[i] = '-';
    }

    for (int i=0; i<n; ++i) {
        printf("%*s%*s  ", DAY_ROW_SIZE-10 , cal[i][0], 10, "");
        print_space_per_calendar(i, n);
    }

    putchar('\n');

    for (int i=0; i<n; ++i) {
        printf("%s", line);
        print_space_per_calendar(i, n);
    }

    putchar('\n');

    for (int i=0; i<n; ++i) {
        for (int j=0; j<WEEK_SIZE; j++) {
            printf("%*s", DAY_SIZE, weeks[(j+wsort) % WEEK_SIZE]);
        }
        print_space_per_calendar(i, n);

    }

    putchar('\n');
    putchar('\n');

    //是否显示最后一行
    int ws= (WEEK_SIZE - (show_last_row(cal, n)? 0: 1));

    for (int i=1; i<ws; ++i) {
        for (int j=0; j<n; ++j) {
            printf("%s", cal[j][i]);
            print_space_per_calendar(j, n);
        }

        putchar('\n');
        putchar('\n');
    }

}

void padding_calendar_space(char cal[][WEEK_SIZE][DAY_ROW_ARR_SIZE], int rows)
{
    char tmp[DAY_ROW_ARR_SIZE];

    sprintf(tmp, "%*s", DAY_ROW_SIZE, " ");


    for (int i=0; i<rows; ++i) {
        for (int j=0; j<WEEK_SIZE; j++)
            strcpy(cal[i][j], tmp);
    }
}

void put_calendar(int y1, int m1, int y2, int m2)
{
    char cal[3][WEEK_SIZE][DAY_ROW_ARR_SIZE];

    int y = y1;
    int m = m1;
    int n = 0;


    while (y <= y2) {
        if (y == y2 && m > m2) break;

        push_calendar(y, m, cal[n++]);

        if (n == 3) {
            print_calendar(cal, n);
            n=0;
        }

        ++m;
        if (m==13) {
            y++;
            m=1;
        }
    }

    if (n % 3) {
        print_calendar(cal, n);
    }
}

/**
 * 日期验证
 * @param int value
 * @param dateunit unit year,month
 * @param char* errinfo 保证验证失败的信息
 *
 */
int validate_date(int value, dateunit unit)
{
    char *err = errinfo;

    switch (unit) {
        case year:
            if (value < MIN_YEAR) {
                sprintf(err, "只能处理%d以后的日期.", MIN_YEAR);
                return 0;
            }

            break;
        case month:
            if (value < 1 || value > 12) {
                sprintf(err, "只能是1~12月.");
                return 0;
            }
            break;
    }
    return 1;
}

/**
 * 输入开始日期和结束日期
 */
int input_date(int *value, dateunit unit) {
    char *err = errinfo;

    do {
        printf("请输入%s份:", unit == year? "年": "月");
        scanf("%d", value);

        if (!validate_date(*value, unit)) {
            puts(err);
            continue;
        }
        return *value;

    } while (1);
}

int getmonth(char *s)
{
    int m=atoi(s);
    if (validate_date(m, month)) {
        return m;
    }

    int i=1;
    for (; i<=12; ++i) {
        int len = strlen(s);
        if (!strncmp(s, months[i], len)) {
            return i;
        }
    }

    return 0;
}

int main(int argc, char** argv)
{
    int y1, y2, m1, m2;

    //使用程序传参
    if (argc > 1) {
        int y, m;

        char* err = errinfo;

        time_t t = time(NULL);
        struct tm *lt = localtime(&t);

        y = lt->tm_year + 1900;
        m = lt->tm_mon + 1;

        //m1
        if (!(m1 = getmonth(argv[1]))) {
            puts("m1:");
            printf("m1:%d\n", m1);

            fprintf(stderr, "%s\n", err);
            return 1;
        }

        //y1
        if (argc > 2) {
            y1 = atoi(argv[2]);
            if (!validate_date(y1, year)) {
                fprintf(stderr, "%s\n", err);
                return 1;
            }

        } else {
            y1 = y;
        }

        //m2
        if (argc > 3) {
            if (!(m2 = getmonth(argv[3]))) {
                fprintf(stderr, "%s\n", err);
                return 1;
            }
        } else {
            m2 = m;
        }

        //y2
        if (argc > 4) {
            y2 = atoi(argv[4]);
        if (!validate_date(y2, year)) {
            fprintf(stderr, "%s\n", err);
            return 1;
        }
        } else {
            y2 = y;
        }

        put_calendar(y1, m1, y2, m2);
        return 0;

    }

    printf("请输入开始日期\n");
    input_date(&y1, year);
    input_date(&m1, month);

    putchar('\n');
    printf("请输入结束日期\n");
    do {
        input_date(&y2, year);

        if (y2 < y1) {
            puts("结束日期应该大于开始日期!");
            continue;
        }

        break;
    } while (1);

    do {
        input_date(&m2, month);

        if ((y2-y1)*12 + (m2 - m1) < 0) {
            puts("结束日期应该大于开始日期!");
            continue;
        }

        break;
    } while (1);

    put_calendar(y1, m1, y2, m2);

    return 0;
}

